package org.abacus.beans;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

public class ClassInfoFactoryImpl extends ClassInfoFactory {
	Map<Class<?>, ClassInfo> classInfos = new HashMap<Class<?>, ClassInfo>();
	GetFilter getFilter = new GetFilter();

	public static class GetFilter {
		private Set<String> startsWithMethod = new HashSet<String>();

		private Set<String> excludeMethod = new HashSet<String>();

		private Set<String> startsWithField = new HashSet<String>();

		private Set<String> excludeField = new HashSet<String>();

		public boolean access(Field f) {
			return !Modifier.isFinal(f.getModifiers()) && !Modifier.isStatic(f.getModifiers());
		}

		public boolean access(Method m) {
			return access(m.getModifiers());
		}

		boolean access(int m) {
			return Modifier.isPublic(m) && !Modifier.isStatic(m) && !Modifier.isFinal(m);
		}

		boolean in(Method m) {
			return in(m.getName(), excludeMethod, startsWithMethod);
		}

		public boolean in(Field f) {
			return in(f.getName(), excludeField, startsWithField);
		}

		boolean in(String s, Set<String> excludeMethod, Set<String> startsWithMethod) {
			if (excludeMethod.isEmpty() && startsWithMethod.isEmpty()) {
				return true;
			}

			if (!excludeMethod.isEmpty() && excludeMethod.contains(s)) {
				return false;
			}
			if (startsWithMethod.isEmpty()) {
				return true;
			}
			for (String sw : startsWithMethod) {
				if (s.startsWith(sw)) {
					return true;
				}
			}
			return false;
		}

	}

	public void startsWithMethod(String s) {
		getFilter.startsWithMethod.add(s);
	}

	public void excludeMethod(String s) {
		getFilter.excludeMethod.add(s);
	}

	public void startsWithField(String s) {
		getFilter.startsWithField.add(s);
	}

	public void excludeField(String s) {
		getFilter.excludeField.add(s);
	}

	@Override
	public synchronized ClassInfo getClassInfo(Class<?> c) {
		ClassInfo classInfo = classInfos.get(c);
		if (null == classInfo) {
			classInfo = new ClassInfoImpl(c, getFilter);
			classInfos.put(c, classInfo);
		}
		return classInfo;
	}

	static class ClassInfoImpl implements ClassInfo {
		Class<?> c;
		Map<Class<?>, FieldInfo> fieldInfos = new HashMap<Class<?>, FieldInfo>();
		Map<Class<?>, MethodInfo> methodInfos = new HashMap<Class<?>, MethodInfo>();
		GetFilter getFilter;

		public ClassInfoImpl(Class<?> c, GetFilter getFilter) {
			super();
			this.c = c;
			this.getFilter = getFilter;
		}

		@Override
		public synchronized FieldInfo getFieldInfo() {
			FieldInfo fieldInfo = fieldInfos.get(c);
			if (null == fieldInfo) {
				fieldInfo = new FieldInfoImpl(c, getFilter);
				fieldInfos.put(c, fieldInfo);
			}
			return fieldInfo;
		}

		@Override
		public synchronized MethodInfo getMethodInfo() {
			MethodInfo methodInfo = methodInfos.get(c);
			if (null == methodInfo) {
				methodInfo = new MethodInfoImpl(c, getFilter);
				methodInfos.put(c, methodInfo);
			}
			return methodInfo;
		}

	}

	static class FieldInfoImpl implements FieldInfo {
		Class<?> c;
		Field[] fields;
		GetFilter getFilter;

		public FieldInfoImpl(Class<?> c, GetFilter getFilter) {
			super();
			this.c = c;
			this.getFilter = getFilter;
		}

		@Override
		public Field[] getFields() {
			if (null == fields) {
				init();
			}
			return fields;
		}

		private void init() {
			fields = init(c).toArray(new Field[0]);
		}

		private Collection<? extends Field> init(Class<?> c) {
			Set<Field> fs = new HashSet<Field>();
			for (Field f : c.getDeclaredFields()) {
				if (getFilter.access(f) && getFilter.in(f)) {
					fs.add(f);
				}
			}
			Class<?> superclass = c.getSuperclass();
			while (null != superclass && superclass != Object.class) {
				fs.addAll(init(superclass));
				superclass = superclass.getSuperclass();
			}
			return fs;
		}

	}

	static class MethodInfoImpl implements MethodInfo {
		Class<?> c;
		Method[] methods;
		GetFilter getFilter;

		public MethodInfoImpl(Class<?> c, GetFilter getFilter) {
			super();
			this.c = c;
			this.getFilter = getFilter;
		}

		@Override
		public Method[] getMethods() {
			if (null == methods) {
				init();
			}
			return methods;
		}

		private void init() {
			methods = init(c).toArray(new Method[0]);
		}

		private Collection<? extends Method> init(Class<?> c) {
			Set<Method> ms = new HashSet<Method>();
			for (Method m : c.getDeclaredMethods()) {
				if (getFilter.access(m) && getFilter.in(m)) {
					ms.add(m);
				}
			}
			Class<?> superclass = c.getSuperclass();
			while (null != superclass && superclass != Object.class) {
				ms.addAll(init(superclass));
				superclass = superclass.getSuperclass();
			}
			return ms;
		}
	}
}
